#include "renderer.h"

Renderer::Renderer(Scene *scene, int widht, int height, int samples, int depth, QObject* parent):
    _rng(),
    _ran(_rng),
    _width(widht),
    _height(height),
    _samples(samples),
    _depth(depth),
    _isStarted(false),
    QThread(parent)
{
    _scene = scene;
    _watcher = new QFutureWatcher<void>();
    _emittersPixelBuffer = new Vector[_width * _height];
    _emittersPixelBufferCopy = new Vector[_width * _height];
#ifdef OPEN_CL
    _OCLAvailable = !_context.create();
    if(_OCLAvailable)
    {
        _program = _context.buildProgramFromSourceFile(QLatin1String(":/raytracer.cl"));
        _raytracer = _program.createKernel("raytracer");
    }
#endif
}

Renderer::~Renderer()
{
    delete _emittersPixelBuffer;
    delete _emittersPixelBufferCopy;
}

void Renderer::render()
{
    _isStarted = true;
    _isPaused = false;
    _watcher->setFuture(QtConcurrent::run(this,&Renderer::_render));
}

void Renderer::update()
{
    _mut.lock();
    memcpy ( _emittersPixelBufferCopy, _emittersPixelBuffer, sizeof(Vector) * _height * _width );
    _mut.unlock();
}

void Renderer::paused()
{
    _mut.lock();
    if(_isStarted && !_isPaused)
        _isPaused = true;
    _mut.unlock();
}

void Renderer::resumed()
{
    _mut.lock();
    if(_isStarted && _isPaused)
        _isPaused = false;
    _mut.unlock();
}

void Renderer::stoped()
{
    _mut.lock();
    if(_isStarted)
    {
        _isPaused = false;
        _isStarted = false;
    }
    _mut.unlock();
    _watcher->waitForFinished();
}


void _renderline(Renderer* r,int y)
{
    for (int x=0; x<r->_width; x++)
    {
        int i = (r->_height - y - 1)* r->_width + x;
        r->_emittersPixelBuffer[i] += r->trace(r->_scene->camera()->getRay(x + r->_ran(),y + r->_ran(),r->_width,r->_height),0);
    }
}

void Renderer::_render()
{
    delete _emittersPixelBuffer;
    delete _emittersPixelBufferCopy;
    _emittersPixelBuffer = new Vector[_width * _height];
    _emittersPixelBufferCopy = new Vector[_width * _height];
    _progress = 0;
    QList<int> yy =  QList<int>();
    QFuture<void> future;
    for (int i=0; i<(_height * _width) - 1; i++)
        _emittersPixelBuffer[i] = Vector(0,0,0);
    for (int y=0; y<_height; y++)
        yy.append(y);
    for (int s=0; s<_samples; s++)
    {
        for (int sy=0; sy<2; sy++)
        {
            for (int sx=0; sx<2; sx++)
            {
                _mut.lock();
                if(!_isStarted)
                {
                    _mut.unlock();
                    return;
                }
                else if(_isPaused)
                {
                    _mut.unlock();
                    bool b = true;
                    while(b)
                    {
                        _mut.lock();
                        if(!_isPaused)
                            b = false;
                        _mut.unlock();
                        Renderer::msleep(20);
                    }
                }
                else
                    _mut.unlock();
//                for (int y=0; y<_height; y++)
//                    _renderline(this,y);
                future = QtConcurrent::map(yy,boost::bind(_renderline, this, _1));
                future.waitForFinished();
                _progress ++;
                emit sampleComputed(_progress);
            }
        }
    }
}


void Renderer::uvw(const Vector &v1, Vector &v2, Vector &v3)
{
    if (fabs(v1[0]) > fabs(v1[1])) {
        double invLen = 1.0 / (v1[0] * v1[0] + v1[2] * v1[2]);
        v2 = Vector(-v1[2] * invLen, 0.0, v1[0] * invLen);
    }
    else {
        double invLen = 1.0 / sqrt(v1[1] * v1[1] + v1[2] * v1[2]);
        v2 = Vector(0.0, v1[2] * invLen, -v1[1] * invLen);
    }
    v3 = v1 / v2;
}

static inline double clamp(double x){ return x<0 ? 0 : x>1 ? 1 : x; }

void Renderer::computeImage(QImage &image, double gamma, double luminosity)
{
    Vector max(0.0,0.0,0.0);

    for(int x = _width - 1; x >= 0; x--)
    {
        for(int y = _height - 1; y >= 0; y--)
        {
            int i = y * _width + x;
            if(max[0] < _emittersPixelBufferCopy[i][0])
                max[0] = _emittersPixelBufferCopy[i][0];
            if(max[1] < _emittersPixelBufferCopy[i][1])
                max[1] = _emittersPixelBufferCopy[i][1];
            if(max[2] < _emittersPixelBufferCopy[i][2])
                max[2] = _emittersPixelBufferCopy[i][2];
        }
    }

    double _max = qMax(max[0],qMax(max[1],max[2]));

    for(int x = _width - 1; x >= 0; x--)
    {
        for(int y = _height - 1; y >= 0; y--)
        {
            int i = y * _width + x;
            QRgb color = qRgb(
                        (int)(clamp(pow(_emittersPixelBufferCopy[i][0] / _max, gamma) * luminosity)*255.0),
                        (int)(clamp(pow(_emittersPixelBufferCopy[i][1] / _max, gamma) * luminosity)*255.0),
                        (int)(clamp(pow(_emittersPixelBufferCopy[i][2] / _max, gamma) * luminosity)*255.0));
            image.setPixel(x,y,color);
        }
    }

}

Vector Renderer::trace(const Ray &ray, int depth)
{
    const Shape* shape = nullptr;
    double distance;
    Vector normal;
    shape = _scene->intersect(ray,distance,normal);
    if (shape == nullptr) return Vector(0,0,0);
    if (++depth>_depth) return shape->emission();

    Vector absorbtion = shape->absorbtion();
    Vector x = ray(distance);//le rayon va de la source à l'endroit du rebond. x est le point de rebond
    Vector n=Normalized(x-shape->position());//On obtient le vecteur

    switch(shape->_dist(_rng))
    {
    case 0:
    {
        double r1=2*M_PI*_ran(), r2=_ran(), r2s=sqrt(r2);
        Vector u, v, w=normal;
        uvw(w,u,v);
        Vector d = Normalized(u*cos(r1)*r2s + v*sin(r1)*r2s+ w*sqrt(1-r2));
        return shape->emission() + absorbtion.Scale(trace(Ray(x,d),depth));
    }
        break;
    case 1:
    {
        return shape->emission() + absorbtion.Scale(trace(Ray(x,ray.direction() - n * 2 * (n * ray.direction())),depth));
    }
        break;
    case 2:
    {
        Ray reflRay(x, ray.direction()-n*2*n*(ray.direction()));
        bool into = n*normal>0;                // Ray from outside going in?
        double nc=1, nt=1.5, nnt=into?nc/nt:nt/nc, ddn=ray.direction()*normal, cos2t;
        if ((cos2t=1-nnt*nnt*(1-ddn*ddn))<0)    // Total internal reflection
            return shape->emission() + absorbtion.Scale(trace(reflRay,depth));
        Vector tdir = Normalized(ray.direction()*nnt - n*((into?1:-1)*(ddn*nnt+sqrt(cos2t))));
        double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c = 1-(into?-ddn:tdir*n);
        double Re=R0+(1-R0)*c*c*c*c*c,Tr=1-Re,P=.25+.5*Re,RP=Re/P,TP=Tr/(1-P);
        return shape->emission() +
                absorbtion.Scale(
                    (depth > 2) ?
                        ((_ran() < P) ?
                             trace(reflRay,depth)*RP
                           :
                             trace(Ray(x,tdir),depth)*TP
                             )
                      :
                        (trace(reflRay,depth) * Re+trace(Ray(x,tdir),depth)*Tr));
    }
        break;
    };
}

void Renderer::saveAsJpeg(QString filename, double gamma, double luminosity)
{
    QImage image(_width,_height,QImage::Format_RGB32);
    computeImage(image,gamma,luminosity);
    image.save(filename,"JPG",100);
}

void Renderer::saveAsEXR(QString filename)
{
    float*r,*g,*b;
    r = new float[_width*_height];
    g = new float[_width*_height];
    b = new float[_width*_height];
    int i = 0;
    _mut.lock();
    for(int x = _width - 1; x >= 0; x--)
    {
        for(int y = _height - 1; y >= 0; y--)
        {
            r[i] = (float)_emittersPixelBufferCopy[i][0];
            g[i] = (float)_emittersPixelBufferCopy[i][1];
            b[i] = (float)_emittersPixelBufferCopy[i][2];
            i++;
        }
    }
    _mut.unlock();
    Header header (_width, _height);
    header.channels().insert ("R", Channel (FLOAT));
    header.channels().insert ("G", Channel (FLOAT));
    header.channels().insert ("B", Channel (FLOAT));
    OutputFile file (filename.toAscii().data(), header);
    FrameBuffer frameBuffer;
    frameBuffer.insert ("R",
                        Slice (FLOAT,
                               (char *) r,
                               sizeof (*r),
                               sizeof (*r) * _width));
    frameBuffer.insert ("G",
                        Slice (FLOAT,
                               (char *) g,
                               sizeof (*g),
                               sizeof (*g) * _width));
    frameBuffer.insert ("B",
                        Slice (FLOAT,
                               (char *) b,
                               sizeof (*b),
                               sizeof (*b) * _width));
    file.setFrameBuffer (frameBuffer);
    file.writePixels (_height);
    delete r;
    delete g;
    delete b;
}

int Renderer::width() const
{
    return _width;
}

int Renderer::height() const
{
    return _height;
}

int Renderer::depth() const
{
    return _depth;
}

int Renderer::samples() const
{
    return _samples;
}

const Scene *Renderer::scene() const
{
    return _scene;
}

bool Renderer::isPaused()
{
    bool b;
    _mut.lock();
    b = _isPaused;
    _mut.unlock();
    return b;
}

bool Renderer::isStarted()
{
    bool b;
    _mut.lock();
    b = _isStarted;
    _mut.unlock();
    return b;
}

void Renderer::setWidth(int value)
{
    _width = value;
}

void Renderer::setHeight(int value)
{
    _height = value;
}

void Renderer::setSamples(int samples)
{
    _samples = samples;
}

void Renderer::setDepth(int depth)
{
    _depth = depth;
}

QDataStream &operator <<(QDataStream & s, const Renderer & value)
{
    return s;
}

QDataStream &operator >>(QDataStream & s, Renderer & value)
{
    return s;
}

#ifdef OPEN_CL
bool Renderer::isOCLAvailable() const
{
    return _OCLAvailable;
}
#endif
